﻿
/**************************************************************************************************************************
 * WordWheel Solver 1.0 by Neil Andrew Rutland 
 * The purpose of this software is to solve a wordwheel puzzle (of dynamic size) by comparing the list of letters with
 * an external dictionary source.
 * 
 * Future improvements/Enhancements
 * -Do Comparison on stream rather than reading into memory
 * -Simplify loops - DONE
 * -Deal with case issues - DONE
 * -Convert to WPF application
 * -Allow for choice of dictionary file in WPF
 * -Get user to set size dynamically so it can change for every game - DONE
 * -Add timers for the Key Functions to quantify improvements - DONE
 * -KidCrazy Suggestion - Use holding arrays instead of .remove/.removeat to improve performance - DONE
 * -Add error handling on input -- Regular expression added to both inputs - DONE
 * ***********************************************************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace WordWheelSolver
{
    class Program
    {

        static string requiredLetter()
        {
            String req = string.Empty;
            while (req.Length == 0)
            {
                Console.Write("Please Enter Your Required Letter: ");
                req = Console.ReadLine().ToLower();

                //Check the input is a single character. If not reset the string and try again
                if (Regex.Match(req, "^[a-z]*$").Success == false || req.Length > 1)
                {
                    Console.WriteLine("Input is Invalid (too manay characters OR not all letters) - Please Try Again'");
                    req = string.Empty;
                }
            }

            Console.WriteLine("Your Required Letter = " + req);
            return req;
        }

        static String getOtherLetters()
        {

            String letters = string.Empty;

            //Loop condition so input can be checked and string reset on error.
            while (letters.Length == 0)
            {
                Console.Write("Please Enter the other required letters: ");

                letters = Console.ReadLine().ToLower();

                char[] otherLetters;
                otherLetters = letters.ToCharArray();

                //Check that the input is only lower case chars - if it isn't go back through the loop
                if (Regex.Match(letters, "^[a-z]*$").Success == false)
                {
                    Console.WriteLine("Your input is Invalid... Please Try Again");
                    letters = string.Empty;
                }
            }

            return letters;
        }

        static int minSize()
        {
            int minimumSize = 0;
            Console.Write("Please enter the minimum length a word can be: ");
            minimumSize = int.Parse(Console.ReadLine());

            return minimumSize;
        }

        static List<string> readDictionary()
        {
            Stopwatch readTime = new Stopwatch();
            readTime.Start();

            string line;
            List<string> wordDictionary = new List<String>();

            // Read the file until it comes to an end
            System.IO.StreamReader file =
            new System.IO.StreamReader("C:\\Users\\neilr\\Documents\\Dictionary.txt");
            while ((line = file.ReadLine()) != null)
            {
                wordDictionary.Add(line);
            }

            file.Close();

            readTime.Stop();
            Console.WriteLine("Read time elapsed: {0}", readTime.Elapsed);


            return wordDictionary;
        }

        
        static List<string> trimDictionary(List<string> allWords, string allLetters, int minWordSize)
        {

            Stopwatch trimTime = new Stopwatch();
            trimTime.Start();

            List<String> tempDictionary = allWords;
            List<String> holdingDictionary = new List<string>();

            int i = 0;
            while (i < tempDictionary.Count)
            {
                //Revision - Combined trimming into 1 logical check. Only words with same or fewer no of letters AND greater than minimum
                //will be returned
                if (tempDictionary[i].Length <= allLetters.Length && tempDictionary[i].Length > minWordSize)
                {
                    holdingDictionary.Add(tempDictionary[i]);
                    i++;
                }
                else
                {
                    i++;
                }
            }

            trimTime.Stop();
            Console.WriteLine("Trim time elapsed: {0}", trimTime.Elapsed);

            return holdingDictionary;
        }

        
        static List<string> matchWords(List<string> trimmedDictionary, string gameLetters, string requiredLetter)
        {
            Stopwatch matchTime = new Stopwatch();
            matchTime.Start();

            int dictLoop = 0;
            List<String> finalHoldingDictionary = new List<string>(); //KC Performance Enhacement Addition

            //Loop through ach item in the trimmed dictionary
            while (dictLoop < trimmedDictionary.Count)
            {
                List<Char> word = trimmedDictionary[dictLoop].ToList();
                List<Char> letters = gameLetters.ToList();

                int lettercount = 0;
                int wordLoop = 0;
                int keyCount = 0;

                //Loop through each letter of the word
                while (wordLoop < word.Count)
                {
                    int letterLoop = 0;
                    
                    //For each letter of the word check it against each letter of the acceptable letters list
                    while (letterLoop < letters.Count)
                    {

                        //Remove letter from list if there is a match to avoid same letter being used more than once
                        if (word[wordLoop] == letters[letterLoop])
                        {
                            letters.RemoveAt(letterLoop);
                            lettercount++;
                            if (word[wordLoop] == requiredLetter[0])
                            {
                                keyCount++;
                            }
                            break;
                        }
                        else
                        {
                            letterLoop++;
                        }
                    }
                    wordLoop++;
                }

                //Remove from dictionary if there is no key letter or the sum of letters is less than total characters in the word (ie all characters not matched)
                if (lettercount < word.Count || keyCount<1)
                {
                    dictLoop++;
                }
                else
                {
                    finalHoldingDictionary.Add(trimmedDictionary[dictLoop]); //KC Performance Enhacement Addition
                    dictLoop++;
                }
            }

            matchTime.Stop();
            Console.WriteLine("Match time elapsed: {0}", matchTime.Elapsed);

            return finalHoldingDictionary;
        }

        static void printMatches(List<string> matchDictionary)
        {
            Stopwatch printTime = new Stopwatch();
            printTime.Start();

            int loop = 0;
            while (loop < matchDictionary.Count())
            {
                Console.WriteLine(matchDictionary[loop]);
                loop++;
            }

            printTime.Stop();
            Console.WriteLine("Print time elapsed: {0}", printTime.Elapsed);

        }

        static void Main(string[] args)
        {
            //Ask for the minimum number of letters and pass this into the trimDictionary function
            string key = requiredLetter();

            string gameLetters = key + getOtherLetters();

            Console.WriteLine( "Letters for this game are: " + gameLetters);

            int minimumSize = minSize();

            Stopwatch totalTime = new Stopwatch();
            totalTime.Start();

            List<String> fullDictionary = readDictionary();

            List<String> condensedDictionary = trimDictionary(fullDictionary, gameLetters, minimumSize);

            List<String> finalDictionary = matchWords(condensedDictionary, gameLetters, key);

            Console.Write("Your matches are: ");
            
            printMatches(finalDictionary);

            totalTime.Stop();
            Console.WriteLine("Total time elapsed: {0}", totalTime.Elapsed);

            Console.ReadKey();
        }
    }
}
